//
// Copyright (C) OpenSim Ltd.
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with this program; if not, see <http://www.gnu.org/licenses/>.
//

#include <set>
#include <iostream>
#include <string>

#include "inet/common/INETUtils.h"
#include "inet/common/ModuleAccess.h"
#include "inet/common/stlutils.h"
#include "inet/common/XMLUtils.h"
#include "inet/networklayer/common/InterfaceEntry.h"
#include "inet/networklayer/common/L3AddressResolver.h"
#include "inet/networklayer/configurator/ipv4/Ipv4NetworkConfigurator.h"
#include "inet/networklayer/contract/IInterfaceTable.h"
#include "inet/networklayer/ipv4/IIpv4RoutingTable.h"
#include "Ipv4NetworkConfiguratorPFC.h"


namespace inet {

Define_Module(Ipv4NetworkConfiguratorPFC);

#define ADDRLEN_BITS    32


void Ipv4NetworkConfiguratorPFC::computeConfiguration()
{
    EV_INFO << "Computing static network configuration (addresses and routes) in ConfiguratorPFC.\n";
    long initializeStartTime = clock();
    topology.clear();
    // extract topology into the Topology object, then fill in a LinkInfo[] vector
    TIME(extractTopology(topology));
    // read the configuration from XML; it will serve as input for address assignment
    //TIME(readInterfaceConfiguration(topology));
    TIME(autoInterfaceConfiguration(topology));
    // assign addresses to Ipv4 nodes
    if (assignAddressesParameter)
        TIME(assignAddresses(topology));
    // read and configure multicast groups from the XML configuration
    TIME(readMulticastGroupConfiguration(topology));
    // read and configure manual routes from the XML configuration
    readManualRouteConfiguration(topology);
    // read and configure manual multicast routes from the XML configuration
    readManualMulticastRouteConfiguration(topology);
    // calculate shortest paths, and add corresponding static routes
    if (addStaticRoutesParameter) {
        cXMLElementList autorouteElements = configuration->getChildrenByTagName("autoroute");
        if (autorouteElements.size() == 0) {
            cXMLElement defaultAutorouteElement("autoroute", "", nullptr);
            TIME(addStaticRoutes(topology, &defaultAutorouteElement));
        }
        else {
            for (auto & autorouteElement : autorouteElements)
                TIME(addStaticRoutes(topology, autorouteElement));
        }
    }
    printElapsedTime("computeConfiguration", initializeStartTime);
}

// void Ipv4NetworkConfiguratorPFC::configureAllInterfaces()
// {
//     ensureConfigurationComputed(topology);
//     EV_INFO << "Configuring all network interfaces.\n";
//     for (int i = 0; i < topology.getNumNodes(); i++) {
//         Node *node = (Node *)topology.getNode(i);
//         for (auto & elem : node->interfaceInfos) {
//             InterfaceInfo *interfaceInfo = static_cast<InterfaceInfo *>(elem);
//             EV_INFO <<"address:"<< interfaceInfo->address<<endl;
//             EV_INFO <<"netmask"<< interfaceInfo->netmask<<endl;
//             if (interfaceInfo->configure){
//                 sendMsgToAdapter(interfaceInfo);
//                 configureInterface(interfaceInfo);
//             }
//         }
//     }
// }

// void Ipv4NetworkConfiguratorPFC::sendMsgToAdapter(InterfaceEntry *interfaceEntry)
// {
//     Enter_Method("configureInterface");
//     ensureConfigurationComputed(topology);
//     auto it = topology.interfaceInfos.find(interfaceEntry->getId());
//     if (it != topology.interfaceInfos.end()) {
//         InterfaceInfo *interfaceInfo = static_cast<InterfaceInfo *>(it->second);
//         if (interfaceInfo->configure){
//             EV_INFO <<"address:"<< Ipv4Address(interfaceInfo->address)<<endl;
//             EV_INFO <<"netmask:"<< Ipv4Address(interfaceInfo->netmask)<<endl;
//             EV_INFO <<"interfaceEntry:"<< interfaceEntry->getInterfaceFullPath()<<endl;
//             //EV_INFO <<"getLocalGateId:"<<it.getLocalGateId()<<endl;

//             // send message to controller adapter
//             ACLMsg *tmp_acl_msg = new ACLMsg;
//             tmp_acl_msg -> setAddressInt(interfaceInfo->address);
//             tmp_acl_msg -> setNetmaskInt(interfaceInfo->netmask);
//             tmp_acl_msg -> setModuleName(interfaceEntry->getInterfaceFullPath().c_str());
//            send(tmp_acl_msg, "networkConfiguratorOutput");

//         }
//     }
// }


void Ipv4NetworkConfiguratorPFC::autoInterfaceConfiguration(Topology& topology)
{
            inetModuleNum = par("inetModuleNum");
            std::set<InterfaceInfo *> interfacesSeen;
            int macineindex = 1;
            int switchIndex = 1+inetModuleNum;
            // configure address/netmask constraints on matching interfaces
            for (auto & linkInfo : topology.linkInfos) {
                for (int j = 0; j < (int)linkInfo->interfaceInfos.size(); j++) {
                    InterfaceInfo *interfaceInfo = static_cast<InterfaceInfo *>(linkInfo->interfaceInfos[j]);

                    cModule *hostModule = interfaceInfo->interfaceEntry->getInterfaceTable()->getHostModule();
                    std::string hostFullPath = hostModule->getFullPath();
                    std::string hostShortenedFullPath = hostFullPath.substr(hostFullPath.find('.') + 1);
                    EV<<"hostFullPath:"<<hostFullPath<<endl;
                    EV<<"hostShortenedFullPath:"<<hostShortenedFullPath<<endl;

                    if(hostShortenedFullPath[0]=='m'){
                        const char *addressAttr =( "192.168."+ std::to_string((macineindex)/256)+"."+ std::to_string((macineindex)%256)).c_str();
                        EV<<( "192.168."+ std::to_string((macineindex)/256)+"."+ std::to_string((macineindex)%256)).c_str()<<endl;
                        const char *netmaskAttr = "255.255.255.255";    // "255.255.x.x"
                        macineindex++;
                        if (interfacesSeen.count(interfaceInfo) == 0) {

                        // parse address/netmask constraints
                        bool haveAddressConstraint = isNotEmpty(addressAttr);
                        bool haveNetmaskConstraint = isNotEmpty(netmaskAttr);

                        uint32_t address, addressSpecifiedBits, netmask, netmaskSpecifiedBits;
                        if (haveAddressConstraint)
                            parseAddressAndSpecifiedBits(addressAttr, address, addressSpecifiedBits);
                        if (haveNetmaskConstraint) {
                            if (netmaskAttr[0] == '/') {
                                netmask = Ipv4Address::makeNetmask(atoi(netmaskAttr + 1)).getInt();
                                netmaskSpecifiedBits = 0xffffffffLU;
                            }
                            else
                                parseAddressAndSpecifiedBits(netmaskAttr, netmask, netmaskSpecifiedBits);
                        }


                        // Note: "hosts", "interfaces" and "towards" must ALL match on the interface for the rule to apply
                        if (true)
                        {
                            EV << "Processing interface configuration for " << interfaceInfo->getFullPath() << endl;

                            // unicast address constraints
                            interfaceInfo->configure = haveAddressConstraint;
                            if (interfaceInfo->configure) {
                                interfaceInfo->address = address;
                                interfaceInfo->addressSpecifiedBits = addressSpecifiedBits;
                                if (haveNetmaskConstraint) {
                                    interfaceInfo->netmask = netmask;
                                    interfaceInfo->netmaskSpecifiedBits = netmaskSpecifiedBits;
                                }
                            }


                            interfacesSeen.insert(interfaceInfo);
                        }
                    }
                    }
                    else if(hostShortenedFullPath[0]=='r'){
                        const char *addressAttr =( "192.168."+ std::to_string((switchIndex)/256)+"."+ std::to_string((switchIndex)%256)).c_str();
                        EV<<( "192.168."+ std::to_string((switchIndex)/256)+"."+ std::to_string((switchIndex)%256)).c_str()<<endl;
                        const char *netmaskAttr = "255.255.255.255";    // "255.255.x.x"
                        switchIndex++;
                        if (interfacesSeen.count(interfaceInfo) == 0) {

                        // parse address/netmask constraints
                        bool haveAddressConstraint = isNotEmpty(addressAttr);
                        bool haveNetmaskConstraint = isNotEmpty(netmaskAttr);

                        uint32_t address, addressSpecifiedBits, netmask, netmaskSpecifiedBits;
                        if (haveAddressConstraint)
                            parseAddressAndSpecifiedBits(addressAttr, address, addressSpecifiedBits);
                        if (haveNetmaskConstraint) {
                            if (netmaskAttr[0] == '/') {
                                netmask = Ipv4Address::makeNetmask(atoi(netmaskAttr + 1)).getInt();
                                netmaskSpecifiedBits = 0xffffffffLU;
                            }
                            else
                                parseAddressAndSpecifiedBits(netmaskAttr, netmask, netmaskSpecifiedBits);
                        }


                        // Note: "hosts", "interfaces" and "towards" must ALL match on the interface for the rule to apply
                        if (true)
                        {
                            EV << "Processing interface configuration for " << interfaceInfo->getFullPath() << endl;

                            // unicast address constraints
                            interfaceInfo->configure = haveAddressConstraint;
                            if (interfaceInfo->configure) {
                                interfaceInfo->address = address;
                                interfaceInfo->addressSpecifiedBits = addressSpecifiedBits;
                                if (haveNetmaskConstraint) {
                                    interfaceInfo->netmask = netmask;
                                    interfaceInfo->netmaskSpecifiedBits = netmaskSpecifiedBits;
                                }
                            }


                            interfacesSeen.insert(interfaceInfo);
                        }
                    }
                    }
                    else EV<<"something is wrong in autoInterfaceConfiguration"<<endl;

                }
            }
    }


}
